import { LiteralUnion, Required } from "./common"
import { InterceptorOptions } from "./interceptor"
import { RetryOptions } from "./retry"

export type SearchParamsInit = string | string[][] | Record<string, string> | URLSearchParams | undefined

export type SearchParamsOption = SearchParamsInit | Record<string, string | number | boolean> | Array<Array<string | number | boolean>>

export type HttpMethod = 'get' | 'post' | 'put' | 'delete' | 'patch' | 'head'

export type Input = string | URL | Request

// Progress - 进度跟踪类型
export type Progress = {
    percent: number
    transferredBytes: number

    /**
     Note: 如果无法获取到（数据 / 请求 / 响应等的）主体大小，那么它的值将会是0
    */
    totalBytes: number
}

// 不直接使用 HeaderInit 类型，原因是 @types/node 没有导出 HeadersInit 类型
export type CenoHeadersInit = 
   | NonNullable<RequestInit['headers']>
   | Record<string, string | undefined>

/**
  Ceno Options - 选项类型
 */
export type CenoOptions = {
    /**
     发送 JSON 的快捷方式。使用此方法替代 `body` 选项。

     接受任何普通对象或值，这些对象或值将被 `JSON.stringify` 转换为 JSON 字符串，并在设置了正确头部的情况下发送到请求体中。
     */
    json?: unknown

    /**
     用户自定义的 JSON 解析函数。

     用例：
     1. 通过 [`bourne` 包](https://github.com/hapijs/bourne) 解析 JSON，以防止原型污染。
     2. 使用 `JSON.parse()` 的 `reviver` 选项。

     @default JSON.parse()

     @example
     ```
     import ceno from 'ceno';
     import bourne from '@hapi/bourne';

     const json = await ceno('https://example.com', {
        parseJson: text => bourne.parse(text)
     }).json();
     ```
     */
    parseJson?: (text: string) => unknown

    /**
     用户自定义的 JSON 序列化函数。

     用例：
     1. 使用自定义的 `replacer` 函数将 JSON 字符串化。

     @default JSON.stringify()

     @example
     ```
     import ceno from 'ceno';
     import { DataTime } from 'luxon';

     const json = await ceno('https://example.com', {
        stringifyJson: data => JSON.stringify(data, (key, value) => {
            if (key.endsWith('_at')) {
                return DataTime.fromJSDate(value as Date).toSeconds();
            }

            return value;
        })
     }).json();
     */
    stringifyJson?: (data: unknown) => string

    /**
     要包含在请求 URL 中的搜索参数。设置此参数将覆盖输入 URL 中所有现有的搜索参数。

     接受 [`URLSearchParams()`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams/URLSearchParams) 支持的任何格值。

     @example
     ```
     import ceno from 'ceno';

     const response = await ceno('https://example.com', {
        searchParams: {
            page: 1,
            limit: 10,
        }
     }).json();

     [ 'https://example.com?page=1&limit=10' ]
     ```
     */
    searchParams?: SearchParamsOption

    /**
     发送请求时，要添加到 `input` URL 前面的前缀。它可以是任何有效的 URL，相对或绝对均可。结尾的斜杠 `/` 是可选的，必要时，在于 `input` 连接时会自动添加。仅当 `input` 为字符串时才生效。使用此选项， `input` 参数不能以斜杠 `/` 开头。

     Note:
      - 将 `prefixUrl` 和 `input` 连接后，结构根据页面的 [基准 URL](https://developer.mozilla.org/en-US/docs/Web/API/Node/baseURI) （如果有）进行解析。
      - 使用此选项时，不允许 `input` 中有前导斜杠 `/`，以确保一致性，并避免因 `prefixUrl` 使用时 `input` 不会遵循正常 URL 解析规则，导致前导斜杠 `/` 含义改变而引起的混淆。
     
      @example
      ```
      import ceno from 'ceno';

      // On http://example.com

      const response = await ceno('demo', {prefixUrl: '/api'});
      //=> http://example.com/api/demo

      const response = await ceno('demo', {prefixUrl: 'https://api.example.com'});
      //=> https://api.example.com/demo
      ```
     */
    prefixUrl?: URL | string

    /**
     一个对象，代表重试配置。 `limit`（重试次数）、`methods`（允许重试的 HTTP 方法）、`statusCodes`（允许重试的 HTTP 状态码）、`afterStatusCodes`（允许配合 `Retry-After` 头部进行重试的 HTTP 状态码）、[`Retry-After`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After)（最大重试时间）。

     如果 `retry` 是 number 类型，它将被作用于 `limit` 属性，其他默认设置将保持不变。

     如果响应提供的 HTTP 状态码包含在 `afterStatusCodes` 中，Ceno 会等待 [`Retry-After`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After) 标头中指定的时间，然后重试请求。如果缺少 `Retry-After` 标头，则会使用非标准的 [`RateLimit-Reset`](https://www.ietf.org/archive/id/draft-polli-ratelimit-headers-02.html#section-3.3) 。
     
     如果 `maxRetryAfter` 设置为 `undefined`，它将使用 `options.timeout`。如果 [`Retry-After`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After) 标头的值大于 `maxRetryAfter`，它将取消请求。

     默认情况下，重试之间的延迟是通过函数 `0.3 * (2 ** (attemptCount - 1)) * 1000` 计算得出的，其中 `attemptCount` 是重试次数，不可以通过传递一个 `delay` 函数来更改此计算方式。

     超时后不会触发重试。
    
     @example
     ```
     import ceno from 'ceno';

     const json = await ceno('https://example.com', {
        retry: {
            limit: 10,
            methods: ['GET']
            statusCodes: [413]
        }
     }).json()
     ```
     */
    retry?: RetryOptions | number

    /**
     请求超时时间（毫秒）。如果请求在指定时间内没有完成，则请求将被取消。不能大于 2147483647。

     限制说明：其最大值不能超过 2147483647 毫秒，这是因为该数值是 32 位有符号整数的最大值（2³¹-1），许多系统或编程语言中对时间参数的定义采用 32 位整数类型，因此存在此上限。

     如果设置为 `false`，则不设置超时时间。

     @default 100000
     */
    timeout?: number | false

    /**
     拦截器配置，可以配置请求拦截器和响应拦截器。

     允许在请求发送前和响应接受后对数据进行处理和转换

     @example
     ```
     import ceno from 'ceno';

     const response = await ceno('https://example.com', {
        interceptors: {
            request: {
                onFulfilled: (request) => {
                    request.headers.set('Authorization', 'Bearer 1234567890');
                    return request;
                },
                onRejected: (error) => {
                    console.error(error);
                }
            },
            response: {
                onFulfilled: (response) => {
                    return response;
                },
                onRejected: (error) => {
                    console.error(error);
                }
            }
        }
     }).json();
     ```
     */
    interceptors?: InterceptorOptions

    /**
     在跟随重定向后，如果响应状态码不是 2xx，则抛出一个 `HttpError`。要在重定向时抛出异常而不是跟随重定向，需将 [`redirect`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters) 选项设置为 `'manual'`。

     如果你正在检测资源可用性并预期会收到错误响应，将此设置为 `false` 可能更合适。

     Note: 如果为 `false`，则错误响应被视为成功，并且不会重试请求。

     @default true
     */
    throwHttpErrors?: boolean

    /**
     下载进度事件处理程序。

     @param progress - 包含下载进度信息的对象
     @param chunk - 接受到的数据。Note: 首次调用时数据为空。

     @example
     ```
     import ceno from 'ceno';

     const response = await ceno('https://example.com', {
        onDownloadProgress: (progress, chunk) => {
            // 示例输出：
			// `0% - 0 of 1271 bytes`
			// `100% - 1271 of 1271 bytes`
			console.log(`${progress.percent * 100}% - ${progress.transferredBytes} of ${progress.totalBytes} bytes`);
        }
     }).json();
     ```
     */
    onDownloadProgress?: (progress: Progress, chunk: Uint8Array) => void

    /**
     上传进度事件处理程序。

     @param progress - 包含上传进度信息的对象
     @param chunk - 已发送的数据。Note: 最后一次调用时数据为空。

     @example
     ```
     import ceno from 'ceno';

     const response = await ceno('https://example.com', {
        onUploadProgress: (progress, chunk) => {
            // 示例输出：
			// `0% - 0 of 1271 bytes`
			// `100% - 1271 of 1271 bytes`
			console.log(`${progress.percent * 100}% - ${progress.transferredBytes} of ${progress.totalBytes} bytes`);
        }
     }).json();
     ```
     */
    onUploadProgress?: (progress: Progress, chunk: Uint8Array) => void

    /**
     用户自定义的 fetch 函数。

     必须与 [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) 标准完全兼容。

     用例：
     1. 使用像 [`isomorphic-unfetch`](https://www.npmjs.com/package/isomorphic-unfetch) 这样的自定义 `fetch` 实现。
     2. 使用一些采用服务端渲染 (SSR) 的框架所提供的 `fetch` 包装函数。

     @default fetch

     @example
     ```
     import ceno from 'ceno';
     import fetch from 'isomorphic-unfetch';

     const json = await ceno('https://example.com', {fetch}).json();
     ```
     */
    fetch?: (input: Input, init?: RequestInit) => Promise<Response>
}

export type CenoOptionsRegistry = {[K in keyof CenoOptions]-?: true}

/**
 选项与 `window.fetch` 中的相同，但 CenoOptions 除外。
 */
export interface Options extends CenoOptions, Omit<RequestInit, 'headers'> {
    /**
     HTTP 方法用于发出请求。
      
     在内部，标准方法 ('GET', 'POST', 'PUT', 'PATCH', 'HEAD', 'DELETE') 采用大写形式，以避免因大小写敏感导致的服务器错误。
     */
    method?: LiteralUnion<HttpMethod, string>

    /**
     HTTP 请求头用于发出请求。

     你可以传递一个 `Headers` 实例或一个对象。

     @example
     ```
     import ceno from 'ceno';

     const url = 'https://lihainuo.com';

     const original = ceno.create({
        headers: {
            hello: 'hello',
            china: 'china',
        }
     });
     */
    headers?: CenoHeadersInit
}

export type InternalOptions = Required<Omit<Options, 'retry'>, 'fetch' | 'prefixUrl' | 'timeout'> & {
    headers: Required<Headers>;
    retry: Required<RetryOptions>;
    prefixUrl: string;
}